﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Linq;
using WordSeg;

namespace MatchBilingualSent
{
    class BiToken
    {
        public string strTerm;
        public List<string> strExplainList;

        public BiToken()
        {
            strTerm = "";
            strExplainList = new List<string>();
        }
    }

    public class MatchSent
    {
        PorterStemmerAlgorithm.PorterStemmer ps;
        WordSeg.WordSeg wordSeg;
        WordSeg.Tokens tokens;
        double m_fMatchThreshold;
        SimilarlySentenceClassifier feaExtract;

        public MatchSent()
        {
            ps = new PorterStemmerAlgorithm.PorterStemmer();
            feaExtract = new SimilarlySentenceClassifier();
        }

        bool is_mark(char c)
        {
            return (c == ' ' || c == '.' || c == ',' || c == '!' || c == '?' || c == '\"' || c == '\'' || c == '-' || c == ':');
        }

        bool is_mark(string s)
        {
            for (int i = 0; i < (int)s.Length; i++)
            {
                if (!is_mark(s[i]))
                {
                    return false;
                }
            }
            return true;
        }

        string StemSent(string strSent)
        {
            string strR = "";
            wordSeg.Segment(strSent, tokens, false);
            for (int i = 0; i < tokens.strSegList.Count; i++)
            {
                string strTerm = tokens.strSegList[i];
                strTerm = ps.stemTerm(strTerm);

                strR += " " + strTerm;
            }
            return strR.Trim();
        }

        List<BiToken> GetTokenExplainFromSent(string strSent)
        {
            List<BiToken> rList = new List<BiToken>();

            wordSeg.Segment(strSent, tokens, false);
            for (int i = 0; i < tokens.strSegList.Count; i++)
            {
                if (tokens.strSegList[i].Trim().Length > 0)
                {
                    BiToken tPhrase = new BiToken();
                    tPhrase.strTerm = tokens.strSegList[i];
                    tPhrase.strExplainList = new List<string>();

                    if (tokens.tokenList[i].strNE.Length > 0)
                    {
                        string[] array = tokens.tokenList[i].strNE.Split(';');
                        tPhrase.strExplainList.AddRange(array);
                    }

                    rList.Add(tPhrase);
                }
            }
            return rList;
        }

        void matchPair(List<string> sen, ref List<SentencePair> spList)
        {
            for (int i = 0; i < sen.Count; i++)
            {
                if (sen[i][0] == 'E')
                {
                    string strRawEngSent = sen[i].Substring(2);
                    List<BiToken> engTokenList;
                    engTokenList = GetTokenExplainFromSent(StemSent(strRawEngSent));

                    double maxSimilar = 0;
                    int maxj = 0;
                    for (int j = 0; j < sen.Count; j++)
                    {
                        if (sen[j][0] == 'C')
                        {
                            string strRawChiSent = sen[j].Substring(2);
                            List<BiToken> chiTokenList;
                            chiTokenList = GetTokenExplainFromSent(StemSent(strRawChiSent));

                            double similar = feaExtract.SimilarlySentence(engTokenList, chiTokenList); // similarTwoSentence(engTokenList, chiTokenList);
                            if (similar > maxSimilar)
                            {
                                maxSimilar = similar;
                                maxj = j;
                            }
                        }
                    }

                    SentencePair sp = new SentencePair();
                    sp.strLine1 = strRawEngSent;
                    sp.strLine2 = sen[maxj].Substring(2);
                    sp.confidence_score = maxSimilar;
                    spList.Add(sp);
                }
            }
        }

        const int maxSentence = 1000;
        int paraMatch(List<string> engPara, List<string> chiPara, ref List<SentencePair> spList)
        {
            int numEng = engPara.Count, numChi = chiPara.Count;
            if (numEng > maxSentence || numChi > maxSentence)
            {
                return -1;
            }

            double[, ,] sim;
            sim = new double[maxSentence, maxSentence, 7];
            for (int i = 0; i < numEng; i++)
            {
                for (int j = 0; j < numChi; j++)
                {
                    List<BiToken> engTokenList;
                    List<BiToken> chiTokenList;

                    //0 + 1
                    sim[i, j, 0] = 0.0;
                    //1 + 0
                    sim[i, j, 1] = 0.0;

                    //1 + 1
                    engTokenList = GetTokenExplainFromSent(StemSent(engPara[i]));
                    chiTokenList = GetTokenExplainFromSent(StemSent(chiPara[j]));
                    sim[i, j, 2] = feaExtract.SimilarlySentence(engTokenList, chiTokenList);
             
                    if (j + 1 < numChi)
                    {
                        //1 + 2
                        engTokenList = GetTokenExplainFromSent(StemSent(engPara[i]));
                        chiTokenList = GetTokenExplainFromSent(StemSent(chiPara[j] + " " + chiPara[j + 1]));
                        sim[i, j, 3] = feaExtract.SimilarlySentence(engTokenList, chiTokenList); 
                    }

                    if (i + 1 < numEng)
                    {
                        //2 + 1
                        engTokenList = GetTokenExplainFromSent(StemSent(engPara[i] + " " + engPara[i + 1]));
                        chiTokenList = GetTokenExplainFromSent(StemSent(chiPara[j]));
                        sim[i, j, 4] = feaExtract.SimilarlySentence(engTokenList, chiTokenList); 
                    }

                    if (j + 2 < numChi)
                    {
                        //1 + 3
                        engTokenList = GetTokenExplainFromSent(StemSent(engPara[i]));
                        chiTokenList = GetTokenExplainFromSent(StemSent(chiPara[j] + " " + chiPara[j + 1] + " " + chiPara[j + 2]));
                        sim[i, j, 5] = feaExtract.SimilarlySentence(engTokenList, chiTokenList);
                    }

                    if (i + 2 < numEng)
                    {
                        //3 + 1
                        engTokenList = GetTokenExplainFromSent(StemSent(engPara[i] + " " + engPara[i + 1] + " " + engPara[i + 2]));
                        chiTokenList = GetTokenExplainFromSent(StemSent(chiPara[j]));
                        sim[i, j, 6] = feaExtract.SimilarlySentence(engTokenList, chiTokenList);
                    }
                }
            }

            double[,] S;
            S = new double[maxSentence + 1, maxSentence + 1];
            int[, ,] link;
            link = new int[maxSentence + 1, maxSentence + 1, 3];

            for (int i = 0; i <= numEng; i++)
            {
                for (int j = 0; j <= numChi; j++)
                {
                    S[i, j] = -1;
                    link[i, j, 0] = link[i, j, 1] = link[i, j, 2] = -2;
                }
            }
            S[0, 0] = 0;
            link[0, 0, 0] = link[0, 0, 1] = link[0, 0, 2] = -1;

            for (int i = 0; i <= numEng; i++)
            {
                for (int j = 0; j <= numChi; j++)
                {
                    if (S[i, j] >= 0)
                    {
                        if (j < numChi)
                        {
                            if (S[i, j] + sim[i, j, 0] > S[i, j + 1])
                            {
                                S[i, j + 1] = S[i, j] + sim[i, j, 0];
                                link[i, j + 1, 0] = i;
                                link[i, j + 1, 1] = j;
                                link[i, j + 1, 2] = 0;
                            }
                        }
                        if (i < numEng)
                        {
                            if (S[i, j] + sim[i, j, 1] > S[i + 1, j])
                            {
                                S[i + 1, j] = S[i, j] + sim[i, j, 1];
                                link[i + 1, j, 0] = i;
                                link[i + 1, j, 1] = j;
                                link[i + 1, j, 2] = 1;
                            }
                        }
                        if (i < numEng && j < numChi)
                        {
                            if (S[i, j] + sim[i, j, 2] > S[i + 1, j + 1])
                            {
                                S[i + 1, j + 1] = S[i, j] + sim[i, j, 2];
                                link[i + 1, j + 1, 0] = i;
                                link[i + 1, j + 1, 1] = j;
                                link[i + 1, j + 1, 2] = 2;
                            }
                        }
                        if (i < numEng && j + 1 < numChi)
                        {
                            if (S[i, j] + sim[i, j, 3] > S[i + 1, j + 2])
                            {
                                S[i + 1, j + 2] = S[i, j] + sim[i, j, 3];
                                link[i + 1, j + 2, 0] = i;
                                link[i + 1, j + 2, 1] = j;
                                link[i + 1, j + 2, 2] = 3;
                            }
                        }
                        if (i + 1 < numEng && j < numChi)
                        {
                            if (S[i, j] + sim[i, j, 4] > S[i + 2, j + 1])
                            {
                                S[i + 2, j + 1] = S[i, j] + sim[i, j, 4];
                                link[i + 2, j + 1, 0] = i;
                                link[i + 2, j + 1, 1] = j;
                                link[i + 2, j + 1, 2] = 4;
                            }
                        }

                        if (i < numEng && j + 2 < numChi)
                        {
                            if (S[i, j] + sim[i, j, 5] > S[i + 1, j + 3])
                            {
                                S[i + 1, j + 3] = S[i, j] + sim[i, j, 5];
                                link[i + 1, j + 3, 0] = i;
                                link[i + 1, j + 3, 1] = j;
                                link[i + 1, j + 3, 2] = 5;
                            }
                        }

                        if (i + 2 < numEng && j < numChi)
                        {
                            if (S[i, j] + sim[i, j, 6] > S[i + 3, j + 1])
                            {
                                S[i + 3, j + 1] = S[i, j] + sim[i, j, 6];
                                link[i + 3, j + 1, 0] = i;
                                link[i + 3, j + 1, 1] = j;
                                link[i + 3, j + 1, 2] = 6;
                            }
                        }
                    }
                }
            }
          
            int x = numEng;
            int y = numChi;
            int k;
            while (link[x, y, 0] >= 0 && link[x, y, 1] >= 0)
            {
                int ii = link[x, y, 0];
                int jj = link[x, y, 1];
                k = link[x, y, 2];
                SentencePair sp = new SentencePair();
                sp.confidence_score = sim[ii, jj, k];
                sp.strLine1 = String.Join(" ", engPara.GetRange(ii, x - ii));
                sp.strLine2 = String.Join(" ", chiPara.GetRange(jj, y - jj));
                spList.Add(sp);
                x = ii;
                y = jj;
            }          
            return 0;
        }

        void matchBlock(List<string> block, List<int> blockIndex, ref List<SentencePair> spList)
        {
            int k, i;
            List<string> engPara, chiPara;
            engPara = new List<string>();
            chiPara = new List<string>();
            for (k = 0; k < blockIndex.Count - 1; k++)
            {
                for (int m = k + 1; m < blockIndex.Count - 1; m++)
                {
                    if (block[blockIndex[k]][0] == 'E' && block[blockIndex[m]][0] == 'C')
                    {
                        engPara.Clear();
                        chiPara.Clear();
                        for (i = blockIndex[k]; i < blockIndex[k + 1]; i++)
                            engPara.Add(block[i].Substring(2));
                        for (i = blockIndex[m]; i < blockIndex[m + 1]; i++)
                            chiPara.Add(block[i].Substring(2));
                        paraMatch(engPara, chiPara, ref spList);
                    }
                    else if (block[blockIndex[k]][0] == 'C' && block[blockIndex[m]][0] == 'E')
                    {
                        engPara.Clear();
                        chiPara.Clear();
                        for (i = blockIndex[k]; i < blockIndex[k + 1]; i++)
                            chiPara.Add(block[i].Substring(2));
                        for (i = blockIndex[m]; i < blockIndex[m + 1]; i++)
                            engPara.Add(block[i].Substring(2));
                        paraMatch(engPara, chiPara, ref spList);
                    }
                }
            }
        }

        List<SentencePair> process(ref List<SentenceToken> stList)
        {
            List<SentencePair> spList = new List<SentencePair>();
            List<string> sentence = new List<string>();
            List<string> block = new List<string>();
            List<int> blocknum = new List<int>();
            string line;
            for (int i = 0; i < stList.Count; i++)
            {
                SentenceToken st = stList[i];
                switch (st.type)
                {
                    case OutputType.E_TYPE:
                        line = "E " + st.strSent;
                        sentence.Add(line);
                        break;
                    case OutputType.C_TYPE:
                        line = "C " + st.strSent;
                        sentence.Add(line);
                        break;
                    case OutputType.B_TYPE:
                        if (blocknum.Count == 0 || blocknum[blocknum.Count - 1] != block.Count)
                        {
                            blocknum.Add(block.Count);
                        }

                        i++;
                        while (i < stList.Count && stList[i].type != OutputType.L_TYPE)
                        {
                            st = stList[i];
                            switch (st.type)
                            {
                                case OutputType.E_TYPE:
                                    line = "E " + st.strSent;
                                    block.Add(line);
                                    break;
                                case OutputType.C_TYPE:
                                    line = "C " + st.strSent;
                                    block.Add(line);
                                    break;
                                default:
                                    Console.WriteLine("Process read block error! {0}", st.type);
                                    return null;
                            }
                            i++;
                        }
                        break;
                    default:
                        Console.WriteLine("Process read data error! {0}", st.type);
                        return null;

                }
            }
            if (blocknum.Count == 0 || blocknum[blocknum.Count - 1] != block.Count)
            {
                blocknum.Add(block.Count);
            }

            matchPair(sentence, ref spList);
            matchBlock(block, blocknum, ref spList);

            List<SentencePair> new_spList = MergeResult(spList);

            return new_spList;
        }

        List<SentencePair> MergeResult(List<SentencePair> spList)
        {
            List<SentencePair> new_spList = new List<SentencePair>();
            Dictionary<string, SortedDictionary<double, string>> dict = new Dictionary<string, SortedDictionary<double, string>>();

            foreach (SentencePair sp in spList)
            {
                if (dict.ContainsKey(sp.strLine1) == false)
                {
                    dict.Add(sp.strLine1, new SortedDictionary<double, string>());
                }
                if (dict[sp.strLine1].ContainsKey(sp.confidence_score) == false)
                {
                    dict[sp.strLine1].Add(sp.confidence_score, sp.strLine2);
                }
            }

            List<SentencePair> spList_1 = new List<SentencePair>();
            foreach (KeyValuePair<string, SortedDictionary<double, string>> pair in dict)
            {
                foreach (KeyValuePair<double, string> subpair in pair.Value.Reverse())
                {
                    SentencePair sp1 = new SentencePair();
                    sp1.strLine1 = pair.Key.Trim();
                    sp1.strLine2 = subpair.Value.Trim();
                    sp1.confidence_score = subpair.Key;

                    spList_1.Add(sp1);
                    break;
                }
            }

            dict.Clear();
            foreach (SentencePair sp in spList)
            {
                if (dict.ContainsKey(sp.strLine2) == false)
                {
                    dict.Add(sp.strLine2, new SortedDictionary<double, string>());
                }
                if (dict[sp.strLine2].ContainsKey(sp.confidence_score) == false)
                {
                    dict[sp.strLine2].Add(sp.confidence_score, sp.strLine1);
                }
            }

            List<SentencePair> spList_2 = new List<SentencePair>();
            foreach (KeyValuePair<string, SortedDictionary<double, string>> pair in dict)
            {
                foreach (KeyValuePair<double, string> subpair in pair.Value.Reverse())
                {
                    SentencePair sp2 = new SentencePair();
                    sp2.strLine1 = subpair.Value.Trim();
                    sp2.strLine2 = pair.Key.Trim(); ;
                    sp2.confidence_score = subpair.Key;

                    spList_2.Add(sp2);
                    break;
                }
            }

            for (int i = 0; i < spList_1.Count; i++)
            {
                for (int j = 0; j < spList_2.Count; j++)
                {
                    if (spList_1[i].strLine1 == spList_2[j].strLine1 &&
                        spList_1[i].strLine2 == spList_2[j].strLine2 &&
                        spList_1[i].strLine1.Length > 0 &&
                        spList_1[i].strLine2.Length > 0)
                    {
                        if (spList_1[i].confidence_score >= m_fMatchThreshold)
                        {
                            new_spList.Add(spList_1[i]);
                        }
                        break;
                    }
                }
            }

            return new_spList;

        }


        public void Initialize(WordSeg.WordSeg w, double matchThreshold = 0.18)
        {
            wordSeg = w;
            tokens = wordSeg.CreateTokens(1024);
            m_fMatchThreshold = matchThreshold;

            feaExtract.Initialize(w);
        }

        public List<SentencePair> Match(List<SentenceToken> stList)
        {
            List<SentencePair> spList;
            spList = process(ref stList);
            return spList;
        }
    }
}
